package week11;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;

import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

public class JedisDistLock implements DistLock {
    private final String lockTag;
    private String resourceName;
    private final JedisPool pool;
    public JedisDistLock(JedisPool pool, String resourceName, String prefix) {
        this.pool = pool;
        this.resourceName = resourceName;
        this.lockTag = prefix + "-" + UUID.randomUUID().toString();
    }
    @Override
    public boolean tryLock(long timeout, TimeUnit unit) {
        if (timeout <= 0) {
            throw new IllegalArgumentException("Illegal argument Timeout=" + timeout + ", TimeUnit=" + unit.name());
        }
        long timeoutInMillis = unit.toMillis(timeout);
        long start = System.currentTimeMillis();
        while (System.currentTimeMillis() - start < timeoutInMillis) {
            if (lockSuccess()) {
                return true;
            }
            Thread.yield(); // in case dead loop CPU 100%
        }
        return false;
    }

    @Override
    public void lock() {
        while (!lockSuccess()) {
            Thread.yield(); // in case dead loop CPU 100%
        }
    }

    @Override
    public void unlock() {
        String unlockScript = "if redis.call(\"get\",KEYS[1]) == ARGV[1]"
                + " then"
                + "   return redis.call(\"del\",KEYS[1])"
                + " else"
                + "   return 0"
                + " end";
        try (Jedis jedis = pool.getResource()) {
            jedis.eval(unlockScript,
                    Collections.singletonList(resourceName),
                    Collections.singletonList(getLockValue()));
        }
    }

    private boolean lockSuccess() {
        try (Jedis jedis = pool.getResource()) {
            // setnx java.camp.LOCK {UUID} 15s
            String result = jedis.set(resourceName, getLockValue(), SetParams.setParams().nx().ex(15L));
            System.out.println(Thread.currentThread().getName() + "-" + Thread.currentThread().getId() + "=" + result);
            return "OK".equals(result);
        }
    }

    private String getLockValue() {
        return lockTag + "-" + Thread.currentThread().getName()
                + "-" + Thread.currentThread().getId();
    }

}
